/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.nhncorp.neptune.common.aop;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nhncorp.neptune.common.LockServiceFactory;
import com.nhncorp.neptune.common.conf.NConfiguration;
import com.nhncorp.neptune.common.exception.TestCaseException;
import com.nhncorp.neptune.master.NeptuneMaster;
import com.nhncorp.pleiades.client.LockEventData;
import com.nhncorp.pleiades.client.LockEventFilter;
import com.nhncorp.pleiades.client.LockEventMonitor;
import com.nhncorp.pleiades.client.LockService;
import com.nhncorp.pleiades.client.EventService.Events;
import com.nhncorp.pleiades.protocol.LockMetaData;

public class TestHandler  extends DefaultInvocationHandler {
  public static final Log LOG = LogFactory.getLog(NeptuneMaster.class.getName());
  
  public static final String TEST_LOCK_PATH = "/test";
  
  private LockService lockService;
  
  private static Map<String, Map<String, UnitTestDetail>> unitTestDetails = new HashMap<String, Map<String, UnitTestDetail>>();
  
  private Object monitor = new Object();
  
  private Random rand = new Random();
  
  private Method currentMethod;
  
  private String lockOwner;
  
  public TestHandler() throws IOException {
    lockOwner = "TestHandler_" + String.valueOf(System.nanoTime() + rand.nextInt());
    lockService = LockServiceFactory.getLockService(new NConfiguration(), lockOwner, true);
    
    //FIXME Test를 위해서는 Test target class가 Test 대상 객체가 event 발생 전에 미리 생성되어 있어야 한다.
    //TabletDropAction의 경우 drop() 이벤트가 발생해야지만 생성되지만 이미 테스트 관련 이벤트는 생성되었기 때문에
    //TestLockEventProcessor가 수행되지 않는다.
    
    lockService.getEventHandler().addLockEventMonitor(new TestLockEventMonitor());
    //System.out.println(">>>>>>>Handler created>" + this);
  }
  
  public void setTargetObject(ProxyObject targetObject) {
    super.setTargetObject(targetObject);
//    if(targetObject instanceof TabletServer) {
//      System.out.println("TestHandler created:lockOwner=" + lockOwner + ", targetObject=" + targetObject + ">" + this);
//    }
  }
  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    UnitTestDetail unitTestDetail = null;
    currentMethod = method;
    String methodName = method.getName();
    if(!"getTestHandlerKey".equals(methodName) && !"init".equals(methodName)) {
      synchronized(unitTestDetails) {
        Map<String, UnitTestDetail> testMethods = unitTestDetails.get(targetObject.getTestHandlerKey());
        if(testMethods != null) {
          unitTestDetail = testMethods.get(methodName);
        }
//        if("dropTable".equals(methodName)) {
//          System.out.println("====Call Start:" + targetObject.getTestHandlerKey() + "," + methodName + 
//              ",unitTestDetail=" + unitTestDetail + ",testMethods=" + testMethods);
//        }
        if(hasUnitTest(unitTestDetail, true)) {
          boolean continueYn = unitTestDetail.getTestProcessor().runTest(this, unitTestDetail);
          unitTestDetail.setAlreadyRun(true);
          if(!continueYn) {
            return null;
          }
        }
      }
    }
    Object result = method.invoke(targetObject, args);
    
    if(hasUnitTest(unitTestDetail, false)) {
      boolean continueYn = unitTestDetail.getTestProcessor().runTest(this, unitTestDetail);
      unitTestDetail.setAlreadyRun(true);
      //System.out.println("====Call End:" + method.getName() + "continueYn=" + continueYn + ",unitTestDetail=" + unitTestDetail);
      if(!continueYn) {
        throw new TestCaseException(method.getName());
      }
    }
    return result;
  }
  
  public LockService getLockService() {
    return lockService;
  }
  
  public String getLockOwner() {
    return lockOwner;
  }
  
  public void removeCurrentTestMethod() {
    //System.out.println("Remove Test Case1:targetObject=" + targetObject.getTestHandlerKey() + ",methodName=" + currentMethod.getName());
    synchronized(unitTestDetails) {
      Map<String, UnitTestDetail> testMethods = unitTestDetails.get(targetObject.getTestHandlerKey());
      if(testMethods == null) {
        return;
      }
      synchronized(testMethods) {
        testMethods.remove(currentMethod.getName());
      }
      String lockId = TestHandler.TEST_LOCK_PATH + "/" + currentMethod.getName();
      try {
        lockService.deleteNode(lockId, true);
      } catch (IOException e) {
        LOG.error("fail to delete node lockID[" + lockId + "]");
      }
    }
  }
  
  private boolean hasUnitTest(UnitTestDetail unitTestDetail, boolean before) {
    return unitTestDetail != null && 
              unitTestDetail.getTargetClass().equals(targetObject.getClass().getName()) && 
              unitTestDetail.isBefore() == before ;
  }
  
  class TestLockEventMonitor implements LockEventMonitor {
    public void notifyEvent(LockService.Events eventType, LockEventData eventData) {
      try {
        synchronized(monitor) {
          String lockId = eventData.getLockId();
          //System.out.println("================= TestHandler notifyEvent: " + lockId + "," + (eventType == LockService.Events.NODE_ADDED));
          String methodName = LockServiceFactory.getLockPathName(eventData);
          
          if(eventType == LockService.Events.NODE_ADDED) {
            LockMetaData lockMetaData = lockService.getMetaData(lockId);
            if(lockMetaData == null) {
              return;
            }
            
            UnitTestDetail unitTestDetail = new UnitTestDetail(lockMetaData.getContentKey());
            
            //테스트 대상 클래스와 틀린 경우는 이벤트 무시
            if(!unitTestDetail.getTargetClass().equals(targetObject.getClass().getName())) {
              return;
            }
    
            //특정 메소드에 테스트 기능을 추가한 경우
            int retry = 0;
            while(retry < 5) {
              lockMetaData = lockService.getMetaData(lockId);
              if(lockMetaData == null) {
                //LOG.debug("NotifyEvent for Test. but no lock meta data1:" + lockId);
                return;
              }
              if(lockService.getLock(lockId, LockService.LockMode.LOCK_EXCLUSIVE)) { 
                lockMetaData = lockService.getMetaData(lockId);
                if(lockMetaData == null) {
                  LOG.debug("NotifyEvent for Test. but no lock meta data2:" + lockId);
                  return;
                }
                if(unitTestDetail.getTestProcessor().isTestTarget(TestHandler.this)) {
                  synchronized(unitTestDetails) {
                    Map<String, UnitTestDetail> testMethods = unitTestDetails.get(targetObject.getTestHandlerKey());
                    if(testMethods == null) {
                      testMethods = new HashMap<String, UnitTestDetail>();
                      unitTestDetails.put(targetObject.getTestHandlerKey(), testMethods);
                    }
                    synchronized(testMethods) {
                      testMethods.put(methodName, unitTestDetail);
                    }
                    System.out.println("[Added Test Case:lockId=" + lockId + "," + 
                        targetObject.getTestHandlerKey() + "," + 
                        targetObject.getClass().getName() + "," + 
                        methodName + "]");
                  }
//                  if(targetObject instanceof TabletServer) {
//                    System.out.println("Added Test Case:lockId=" + lockId + ",targetObject=" + targetObject.getTestHandlerKey() + "," + ((TabletServer)targetObject).getHostName() + ",testDetail=" + unitTestDetail);
//                  } else if(targetObject instanceof NeptuneMaster) {
//                    System.out.println("Added Test Case:lockId=" + lockId + ",targetObject=" + targetObject.getTestHandlerKey() + "," + ((NeptuneMaster)targetObject).getHostName() + ",testDetail=" + unitTestDetail);
//                  } else {
//                    System.out.println("Added Test Case:lockId=" + lockId + ",targetObject=" + targetObject.getTestHandlerKey() + ",testDetail=" + unitTestDetail + ", testHandler=" + testHandler);
//                  }
                  //가장 먼저 lock을 가져온 server가 처리하고 lock을 삭제한다.
                  if(unitTestDetail.isOnlyOneTarget()) {
                    lockService.deleteNode(lockId, true);
                  } else {
                    lockService.releaseLock(lockId, LockService.LockMode.LOCK_EXCLUSIVE);
                  }
                } else {
                  lockService.releaseLock(lockId, LockService.LockMode.LOCK_EXCLUSIVE);
                }
                break;
              }
              try {
                Thread.sleep(2000);
              } catch (InterruptedException e) {
                return;
              }
              retry++;
            }
            if(retry >= 5) {
              return;
            }
          } else {
            //특정 메소드에 테스트 기능을 삭제한 경우
            synchronized(unitTestDetails) {
              Map<String, UnitTestDetail> testMethods = unitTestDetails.get(targetObject.getTestHandlerKey());
  //            if(testMethods == null) {
  //              System.out.println("Remove Test Case2:targetObject=" + targetObject.getTestHandlerKey() + ",methodName=" + methodName + ",testMethods=null");
  //            } else {
  //              System.out.println("Remove Test Case2:targetObject=" + targetObject.getTestHandlerKey() + ",methodName=" + methodName + ",testMethods=" + testMethods.containsKey(targetObject.getClass().getName() + methodName));
  //            }
              if(testMethods == null) {
                return;
              }
              synchronized(testMethods) {
                UnitTestDetail unitTestDetail = testMethods.get(methodName);
                if(unitTestDetail != null && unitTestDetail.isAlreadyRun()) {
                  System.out.println("Removed Test Case:" + targetObject.getClass().getName() + "," + targetObject.getTestHandlerKey() + "," + methodName);
                }
              }
            }
          }
        }
      } catch (Exception e) {
        LOG.error(e);
      }
    }

    public LockEventFilter getFilter() {
      LockEventFilter filter = new LockEventFilter();
      filter.addConstraint(LockEventFilter.Operator.PF, TEST_LOCK_PATH);
      
      List<LockService.Events> lockEvents = new ArrayList<LockService.Events>();
      lockEvents.add(LockService.Events.NODE_ADDED);
      lockEvents.add(LockService.Events.NODE_REMOVED);
      filter.setLockEventList(lockEvents);
      
      return filter;
    }

  }
  
  public static void clear() {
    unitTestDetails.clear();
  }
}
